JDK8新特性

Lambda表达式

  1. lambda只适用于方法参数列表是函数式接口的情况,函数式接口即接口中有且仅有一个抽象方法的接口【lambda表达式相当于对函数式接口抽象方法的重写】

  2. 老的匿名内部类编译以后的字节码文件,发现匿名内部类实际上编译会生成实现对应接口的一个新的类,类的名称为接口名$1

    使用了Lambda后XJad反编译工具无法反编译 ,可以使用jdk自带的工具javap对使用了lambda的类的字节码文件进行反汇编,使用方法是在DOS命令行输入javap -c -p 文件名.class

  3. Lambda表达式会在当前类中生成一个名为lambda$上级方法名如main$0的私有静态方法,lambda表达式中的代码会放在这个新增的方法的方法体中,在运行的时候会在方法中生成一个名为lambda表达式所在的类$$Lambda$1的匿名内部类并重写其中的抽象方法,重写方法的时候会在重写方法中调用lambda表达式中生成的私有静态方法lambda$上级方法名如main$0,即lambda表达式会生成匿名内部类和lambda表达式中写入方法对应的静态方法

    运行时生成的类可以通过命令java -Djdk.internal.lambda.dumpProxyClasses 要运行的包名.类名将运行时生成的内部类class码输出到一个文件中

    lambda表达式就是对函数式接口的实现并且重写其中的抽象方法

 

使用条件

  1. 方法参数或者局部变量的类型必须是函数式接口才能使用Lambda表达式

    其他参数类型或者参数类型是抽象类都不能使用Lambda表达式

    局部变量单独定义时也可以定义成匿名内部类的形式,这种情况也可以把匿名内部类写成Lambda表达式的结构

    函数式接口:有且只有一个抽象方法的接口,接口上使用注解@FunctionalInterface能够检测接口中是否只含有一个抽象方法

  2. Lambda表达式和匿名内部类使用上的区别

    • 匿名内部类实现的可以是类、抽象类、接口;Lambda表达式需要的类型必须是函数式接口

      • 匿名内部类实例

        Person是现存的一个类,new Person(){}的意思是创建一个匿名内部类继承Person类,并实例化成一个对象,抽象类中没有抽象方法可以不重写,有抽象方法需要重写

    • 匿名内部类实现的类、抽象类、接口中抽象方法的数量随意;Lambda表达式实现的只能是函数式接口

    • 匿名内部类在编译后生成单独的class,Lambda表达式在程序运行时动态生成匿名内部类

       

 

 

表达式格式

  1. 标准格式

    参数列表是参数类型 参数值,参数类型 参数值,...,函数不需要参数只写括号

    lambda表达式是对函数式接口中的抽象方法的视线,用在方法需要函数式接口实例的情况下,直接写Lambda表达式相当于写了实现了函数式接口并创建了一个匿名内部类并实现了抽象方法,该实例可以在方法中执行实例对应的抽象方法

    箭头的作用是分割参数和方法体

    【语法】

    【实例】

  2. 省略格式

    • 参数列表的参数类型可以省略

    • 如果参数列表只有一个参数,参数类型和括号都可以省略

    • 如果大括号内有且只有一个语句,可以同时省略大括号、return和语句分号

      以上省略大括号、return和语句分号三个部分必须同时省略

       

常用内置函数式接口

内置函数式接口主要在java.util.function包下

  1. 方法声明时可以直接对未实现的抽象方法进行调用和传参

  2. 常用的内置函数式接口

    • Supplier接口

      供给型接口,不给参数但是返回返回值的接口,可以对当前方法括号外的局部变量或者成员变量进行操作

      • 应用举例

        打断点查看执行过程会发现先跳到printMax方法中执行supplier.get()前的方法,执行到supplier.get()方法才会跳到Lambda表达式中去执行Lambda表达式的代码

    • Consumer接口

      消费型接口,需要参数,但是没有返回值

      只需要使用传入的参数但是不用返回返回值,比如对数组排序,将字符串转换成大写或者小写

      • 应用举例

    • Function接口

      传入一个参数,经过加工以后返回另一个数据

      这个地方结合之前学的super和泛型好好看一下,实际课堂只讲了apply和andThen,andThen的逻辑和Consumer是一样的

      • 应用实例

    • Predicate接口

      传入一个数据,根据逻辑对参数进行判断,返回true或者false;判断型接口

      • 应用实例

方法引用

lambda表达式仍然可能存在冗余的场景【lambda表达式的方法体直接调用的现成的方法体,显得比较冗余】,结合方法引用可以进一步的简化Lambda表达式

  1. 方法引用在Lambda表达式中的实例

    • 方法引用的方法的参数列表需要和函数式接口的抽象方法的参数列表保持一致

    • 被引用方法与抽象方法需要有相同类型的返回值

      以上两个条件不满足编译就会报错

  2. 常见方法引用的方式

    除去对静态方法的方法引用外还有多种其他方法引用方式,列举如下

    • instanceName::methodName 对象::实例方法名

      • 实例

    • ClassName::staticMethodName 类名::静态方法

      • 实例

    • ClassName::methodName 类名::实例方法名

      平常使用的时候,类名是无法直接调用实例方法的,Lambda表达式对这种调用方式进行了特殊处理,使用类名::实例方法名进行方法引用时在方法调用的时候会自动将第一个参数作为方法的调用者,即本质上还是通过对象去调用的实例方法,调用时后续参数才是参数列表的内容,泛型对应第一个是对象的类型、参数列表、返回值类型

      疑问:函数式接口中的抽象方法能被重载吗

      • 实例

    • ClassName::new 类名::new 引用类的构造器

      类名::new的方式引用类的构造器,这种引用方式调用类的无参构造还是有参数构造主要看函数式接口的泛型数量和抽象方法的传参个数,暂时把抽象方法的参数列表看成对应构造方法的重载,参数列表就是对应构造器的参数列表

      • 实例

    • TypeName[]::new String[]::new 调用数组的构造器

      int[]和String[]也可以看做一个类

      • 实例

         

 

集合的Stream流式操作

Stream流和IO流没有任何关系,流式思想类似于工厂车间的生产流水线,Stream不是一种数据结构,不保存数据,而是对数据进行加工处理,Stream可以看做是流水线上的一个工序,在流水线上通过多个工序让一个原材料加工成一个商品

一般的Stream流都是单线程的,效率不够高;还有并行的Stream流

  1. 案例初体验

    • 实例

       

获取Stream流

  1. 通过Collection接口中的默认方法default Stream<E> stream()获取Stream流

    返回值是Stream类型

    Map接口和Collection接口没有什么关系,所以Map接口没有stream方法,Map接口可以通过map.keySet()方法得到key的set集合可以使用stream方法;

    map.value()方法可以得到所有value值并放入Collection集合中使用stream方法得到stream流

    map.entrySet()方法可以得到键值对的set集合也可以通过stream方法得到stream流

  2. 通过Stream中的静态方法static<T> Stream<T> of(T... values)也可以获取流

    可变长度参数的本质是一个数组,

    • 实例

 

Stream流的API

根据返回结果Stream流的方法可以分为终结方法和非终结方法,区分标准就是非终结方法的返回值类型是Stream,而终结方法的返回值类型不是Stream,可能是long或者void;非终结方法也叫函数拼接方法,即返回值仍然是Stream本身,支持链式调用

  1. Stream流的常用方法

    不是全部,更多方法参考API文档

    • long-->stream.count()

      • 作用:统计Stream流中元素的个数

      • 实例

    • void-->stream.forEach(Consumer<? super String> action)

      • 作用:遍历流中的数据,对流中的元素进行统一处理

      • 实例

    • Stream-->stream.filter(Perdicate perdicate)

      • 作用:过滤掉一些元素,返回符合过滤条件的数据

      • 实例

    • Stream-->stream.limit(Integer count)

      • 作用:取用流中的前几个元素

      • 实例

    • Stream-->stream.skip(Integer count)

      • 作用:跳过流中的前count个元素

      • 实例

    • Stream-->stream.map(Function function)

      • 作用:映射,功能是将流中的元素类型转换成另外一种类型,比如字符串转换成Integer类型,同时还可以返回原类型,但是修改了对应元素的属性

      • 备注说明:如果Function函数式接口实现是构造方法引用,会自动将元素作为参数调用有参数构造方法

      • 实例

    • Stream-->stream.sorted()

      • 作用:无参sorted根据集合的自然顺序来排序,是字典序吗?

      • 实例

    • Stream-->stream.sorted(Comparator<? super T> comparator)

      • 作用:根据比较器指定的规则排序,需要传参Comparator比较器

      • 实例

    • Stream-->stream.distinct()

      • 作用:去掉流中重复的数据

      • 备注说明:默认情况下是无法去掉自定义的重复数据类型,比如Person;distinct方法判断重复的依据是hashCode和equals方法,重写以后针对自定义类才有判断对象重复的依据

      • 实例

    • Stream-->stream.allMatch(Pewdicate<? super I> predicate)

      • 作用:判断流中的元素是否全部满足指定条件,全部满足返回true,不全部满足返回false

      • 实例

    • Stream-->stream.anyMatch(Pewdicate<? super I> predicate)

      • 作用:判断流中是否存在元素匹配指定条件,存在满足条件的元素返回true,不存在返回false

      • 实例

    • Stream-->stream.noneMatch(Pewdicate<? super I> predicate)

      • 作用:判断流中是否所有元素都不匹配指定条件,都不匹配返回true,存在元素匹配返回false

      • 实例

    • Optional<Integer>-->stream.findFirst()

      • 作用:找到流中的第一个元素

      • 备注说明:返回值类型Optional<Integer>,意思是该方法的结果可能找得到,也可能找不到,值需要使用option.get()方法获取;注意Optional<Integer>-->stream.findAny()的作用和findFirst()一样都是找到第一个元素

      • 实例

    • Optional<Integer>-->stream.max(Comparator<? super Integer> comparator)

      • 作用:找到流中值最大的元素

      • 备注说明:Comparator接口抽象方法o1-o2是升序【看成序号正常升就是升序】,配合Comparator接口其实可以实现单独使用max或者min方法同时获取最大值和最小值的需求

      • 实例

    • Optional<Integer>-->stream.min(Comparator<? super Integer> comparator)

      • 作用:找到流中值最大的元素

      • 实例

    • T-->stream.reduce(T identity,BinaryOperator<T> accumulator)

      • 作用:找到流中值最大的元素

      • 备注说明:Map方法和reduce方法组合使用能大大简化对集合的操作

      • 实例

    • IntStream-->stream.mapToInt(ToIntFunction<? super T> mapper)

      • 作用:可以将Stream<Integer>中的Integer类型转换成int类型

      • 备注说明:使用包装类存在性能问题,Integer占用的内存比int多,Stream流在操作过程中自动装箱拆箱;除了mapToInt还有其他的mapToLong等其他方法可以将元素转成其他的基本数据类型;StreamIntStreamLongStream都继承于BaseStream,区别就是IntStream相对于Stream操作的是基本数据类型而不是包装类

      • 实例

    • Stream<T>-->Stream.concat(Stream<? extends T> a,Stream<? extends T> b)

      • 作用:将两个Stream流合并成一个stream流,是将两个流中的元素

      • 备注说明:被合并的两个stream流的元素类型是相同的,合并后的stream流的元素类型也是一样的;参与合并的流或者中间流不能使用forEach方法,会提示非法状态错误页

      • 实例

  2. Stream流的注意事项

    • 对一个Stream流的方法只能操作一次,重复相同操作会抛非法状态异常,如下所示这种情况就就会抛异常

    • Stream流的非终结方法返回的流是新的流,两个流不是同一个对象

    • Stream不调用终结方法,中间的非终结操作是不会执行的

       

Stream流的数据处理

收集数据

在流操作完成后,可以将Stream流的元素放到集合或者数组中,可以收集流中的数据

  1. 将流中的数据收集到集合中

    • 实例

  2. 将流中的数据搜集到数组中

    • 实例

       

数据聚合计算

聚合计算和数据库的对字段操作是一样的,如获取最大值、最小值、求总和、平均值、统计数量

获取最大值调用max方法也能单独实现,这里面的操作基本直接调用Stream本身的API就能实现

  1. API

    • 实例

       

数据分组

  1. 分组API

    • 对流中的数据根据某个标准进行一级分组

    • 对流中的数据进行多级分组

      逻辑是先针对某个标准进行分组,再在同一个分组中根据另外一个标准再次进行分组;使用的是groupingBy的重载方法,参数列表是Function接口和Collector接口的groupingBy单参方法的返回值【同样需要指定Function接口的匿名实现,即返回第二级分组标准】;Function是第一级分类的标准,Collector是第二级分类的标准

      返回的仍然是一个map,map的key是第一层的分类标准;map的value仍然是一个Map,是二级分类

       

数据分区

数据分区和数据分组类似,但是数据分区只能分成truefalse两个组,数据分组可以分成多个组且还能多级分组

  1. API

    • 实例

数据拼接

将String类型的数据根据指定规则进行字符串拼接

  1. 数据间拼接一个指定间隔字符串

    • 实例

       

并行Stream流

上述的Stream流都是串行Stream流,效率低

  1. 串行Stream流

    对每个元素的处理都使用的同一个线程

  2. 获取并行流的两种方式

    • 方式一:直接通过list集合中的实例方法parallelStream方法获取并行流,list.parallelStream()

    • 方式二:通过串行stream流的实例方法stream.parallelStream()获取对应的并行流

    • 实例

  3. 并行Stream流

    并行流的不同元素处理使用的是不同的线程

  4. 并行stream流的效率

  5. 多线程操作可能存在线程安全问题

    一般的解决方法有给涉及线程安全的操作加锁放在同步代码块中,使用线程安全的集合,或者调用Stream接口的toArray或者collect方法【返回集合或者数组】对数据的操作就是线程安全的

  6. 并行Stream流的实现原理

    • parallelStream是基于Fork/Join框架实现的

      parallelStream底层使用的不是普通的线程,使用的是Fork/Join框架;Fork/Join是JDK7引入的新的线程框架,作用是可以将一个大任务拆分成很多小任务来异步执行

    • Fork/Join框架主要包含三个模块

      • 线程池:ForkJoinPool,继承自Executor,是线程池

      • 任务对象:ForkJoinTask,表示拆分出来的小任务,实现了FutureSerializable接口,实际使用是自定义任务类继承ForkJoinTask的子类

      • 执行任务的线程对象:ForkJoinWorkerThread,继承于Thread

    • Fork/Join框架的原理

      • Fork/Join框架使用分治算法来将大任务拆分成小任务,比如排序将1000w个数分为两个500w个数的排序任务,再对500w个数的排序任务再分割,直到最后的排序任务个数达到一定的阈值才会使用其他排序方法局部排序,排序完成以后对局部有序的结果再合并,Fork是大任务拆分成小任务的过程,Join是小任务合并为大任务的过程

    • 工作窃取算法

      • Fork/Join最核心的地方是利用现代硬件设备多核,利用空闲的CPU提高性能,工作窃取算法的作用是某个线程从其他队列中窃取任务来执行,某个线程把任务执行完空闲以后去别的线程窃取排队最后的任务来执行

    • Fork/Join框架实例

      • 一个计算1-10000的任务,先对任务进行拆分,加法运算的数量大于3000就拆分任务,小于3000就进行运算

        在计算次数较少的情况下,比如1000w次相加,仍然是直接用单线程循环遍历相加会快一些,任务管理器--CPU--图形右键--将图形更改为--逻辑处理器,可以查看CPU的线程占用率;Fork/Join执行任务会所有的CPU线程都打满,而单线程遍历运算只会拉起一个CPU线程的占用率

      • 代码实现

        【求和任务类】

        【代码执行效果】

    • 小结

      • 并行stream流parallelStream是线程不安全的,适用于CPU密集型场景,主要目的还是不要浪费CPU的性能,如果CPU本身就负载很大了还使用并行流,并不能起到很明显的效果,还可能影响其他程序的执行

      • 一般并行Stream流不适用于I/O密集型操作【磁盘I/O、网络I/O都属于I/O操作】,I/O操作一般不依赖CPU,使用并行流进行大批量的消息推送涉及大量I/O,反而会导致使用并行流更慢

      • 并行流使用时无法保证元素的顺序,即使用集合同步流数据即使保证了元素都正确也无法保证不同线程将指定元素按照顺序放在一个集合中

 

 

 

 

接口增强

  1. 增强点

    • JDK8以前的接口只能放静态常量和抽象方法,JDK8对接口进行增强,还可以放默认方法和静态方法

    • JDK8接口

  2. 增强原因

    • 默认方法

      • 接口中只有抽象方法,接口新增抽象方法,所有的实现类都必须重写该抽象方法,不利于接口的扩展

      • 接口中的默认方法实现类不需要重写就可以直接进行使用,实现类也可以根据需要进行重写

        方便接口的扩展

        默认方法在实现类中重写不需要default关键字

      • 默认方法语法格式

        默认方法有方法体,与类中一般方法的定义相比只是多了一个default关键字

    • 静态方法

      • 静态方法不能被实现类直接调用,也不能被实现类重写,只能通过接口名.方法名()进行调用

      • 静态方法语法格式

        与默认方法相比只是关键字default改成了static

  3. 默认方法和静态方法的区别

    • 默认方法必须通过实例调用,静态方法通过接口名调用

    • 默认方法可以被实现类继承,实现类可以直接使用接口定义的默认方法,也可以重写接口中的默认方法;静态方法不能被继承也不能被实现类重写,只能通过接口名进行调用

    • 接口中的方法需要被实现类继承或重写,使用默认方法;不需要被继承或者重写就直接使用静态方法

 

Optional类

Optional类只有两种状态,没有子类,一般看成一个容器,要么Optional中有值,要么就是null;主要作用是避免空指针检查,防止NullPointerException,结合方法orElseifPresentifPresentOrElsemap能够写出优雅的判空处理代码

  1. 传统对指针判空的处理方式

    • 实例

  2. Optional类的API

    • Optional<T> --> Optional.of(T t)

      • 作用:创建一个Optional对象,of方法只能传入具体值,不能传入null,传入null会报空指针异常

    • Optional<T> --> Optional.ofNullable(T t)

      • 作用:创建一个Optional对象,ofNullable方法既能传入具体值,也能传入null

    • Optional<T> --> Optional.empty()

      • 作用:创建一个内容为nullOptional对象

    • boolean --> optional.isPresent()

      • 作用:判断optional中是否有具体值,有具体值就返回true,如果没有值或者值为null就返回false

    • T t-->optional.get()

      • 作用:获取optional中的具体值,如果optional有具体值就返回具体值,没有值或者为null就会抛没有这个元素的异常;所以一般操作都使用optional.isPresent()对是否有值进行判断再决定是否取值进行处理

    • Optional --> optional.map(Function<T> function)

      • 作用//TODO,课上没讲但是在对用户名字转大写案例中使用了,很好用,功能和Stream流的map方法类似,看文档研究研究

  3. 使用Optional类对可能存在空值的情况处理

    • 使用apioptional.isPresent()来判断是否空值进一步处理

    • T --> optional.orElse(T t)

      • 作用:如果optional中有值就返回该具体值,如果optional中没有值就使用传参作为返回值

      • 实例

    • void --> optional.ifPresent(Comsumer<T> comsumer)

      • 作用:如果optional中有具体值就执行函数式接口Consumer的匿名实现,ifPresent方法自动传参optional中的具体值,如果optional中没有值就会什么也不做

      • 实例

    • void --> optional.ifPresentOrElse(Consumer<T> comsumer,Runnable runnable)

      • 作用:optional对象中存在具体值调用Consumer接口,没有值就执行Runnable接口的匿名实现

      • 实例

  4. 需求实现:将用户的名字属性全部转换成大写

    java中的toUpperCase会直接将中文原样输出

     

 

日期格式

Java8中新增了日期和时间的API

新版本中对日期时间的API的操作会生成全新的时间日期对象实例,不会影响老值;新版本给人提供的LocalDateLocalTime、...、ZoneDate来给人使用,提供Instant来给程序使用;通过时间调整器TemporalAdjuster可以更快更精确地调整时间日期;此外还是时间解析线程安全的

新的时间日期类中,LcoalDate表示日期【年月日】,LcoalTime表示时间【时分秒】,LocalDateTime包含日期和时间【年月日时分秒】,DateTimeFormatter对时间进行格式化和解析,主要供程序使用方便对秒和纳秒操作的Instant类,计算日期时间间隔的Duration/Period,方便调整时间的TemporalAdjuster,以及带时区的日期、时间、日期时间,分别为ZoneDateZoneTimeZoneDateTime

  1. 旧版日期时间API的问题

    • 设计不合理,Date类有两个,一个是util包下的,一个sql包下的

    • 对日期时间解析非线程安全

    • 时区处理麻烦,日期类不提供国际化时区支持

    • 实例

  2. 新增时间日期API

    线程安全、API设计合理,全部位于java.time包下,其中的一些关键类

    Java使用的历法是ISO 8601日历系统【世界民用历法,即公历,平年365天,闰年366天】,Java8还支持其他4套历法,分别为ThaiBuddhistDate:泰国佛教历,MinguoDate:中华民国历,JapaneseDate:日本历,HijrahDate:伊斯兰历

    • LocalDate:日期,包含年月日,格式为2019-10-16

      • 实例

    • LocalTime:表示时间,包含时分秒,格式为16:38:54.158549300

      • 实例

    • LocalDateTime:表示日期时间,包含年月日时分秒,格式为2018-09-06T15:33:56.750

      • 实例

        【修改LocalDateTime】

        修改其他的LocalDateLocalTime也是一样的改法;修改对应的LcoalDateTime会生成一个修改后全新的LocalDateTime对象,不会影响原来的对象

        【比较时间】

    • DateTimeFormatter:用于对时间日期进行格式化解析

      • 实例:

    • ZoneDateTime:包含对时区解析的时间

    • Instant:时间戳,一个当前瞬时时间

      • 实例

    • Duration:用于计算两个LocalTime时间间的时长间隔

      • 实例

    • Period:用于计算两个LocalDate日期间的时长间隔

      • 实例

         

时间校正器

自定义调整时间,TemporalAdjuster

  1. TemporalAdjuster

    • 作用:通过实现函数式接口TemporalAdjuster的抽象方法adjustInto实现传入一个时间类型的参数,返回一个经过调整的时间类型参数,传参和返回参数类型都是Temporal类型,是LocalDateTime的父类型引用

    • 备注说明:JDK8自带了很多时间调整器,都是TemporalAdjuster中的静态方法,比如调整到这个月的周几,这个月的第一天,下个月的第一天,下一年的第一天,最后一天等等,返回的也是TemporalAdjuster类型的调整器,和自定义的调整器一样直接通过localDateTime.with(temporalAdjuster)进行调用

    • 实例

  2. 设置日期时间的时区

    LocalDateLocalTimeLcoalDateTime是不带时区的类,带时区的类包括ZoneDateZoneTimeZoneDateTime;

    每个时区都有对应的ID,ID的格式为"区域/城市",如Asia/Shanghai

    ZoneId中包含所有的时区ID,通过APIZoneId.getAvailableZoneIds()获取到时区ID的set集合

    • 实例

       

注解

重复注解

注解是JDK5引入的,注解有个比较大的问题,一个注解不能再同一个位置重复使用,比如注解嵌套这个事情实现比较麻烦,JDK8引入重复注解的概念,允许在相同地方重复使用使用元注解@Repeatable注解定义的重复注解

  1. 重复注解定义和解析

    • 实例

       

类型注解

JDK8为@Target元注解新增了两种属性类型,

TYPE_PARAMETER表示该注解能写在泛型上,类型参数的声明比如<T><T extends Person>,通俗的说就是标注了@Target(ElementType.TYPE_PARAMETER)的注解既可以使用在泛型的前面,注意不能使用在类前面

TYPE_USE表示定义中标注了@Target(ElementType.TYPE_USE)的注解可以在任何用到类型的地方使用

  1. TYPE_PARAMETER

    • 实例

  2. TYPE_USE

    • 实例

      定义中标注了@Target(ElementType.TYPE_USE)的注解可以在任何用到类型的地方使用

       

JDK9新特性

模块化系统

  1. 原来的缺点

    • 在Java8及以前,java中用到的自带的类【String、集合等】都在JDK/jre/lib/rt.jar文件夹中,这个目录下的类的包名就是java.xxx.Xxx了,这样会使java的运行显得臃肿和笨重,例如执行代码String str="hello",类加载器需要去jrert.jar包下大量搜索对应类的文件并加载到JVM中,很多没用到的类都一股脑塞到jre目录下,对JVM管理来说增加非必要负担和性能消耗,Java9模块化可以按需自定义java平台提供的类,所以jdk9的文件夹下没有jre目录

      模块化能由于按需自定义java平台类,减少内存开销、提高效率

    • 权限修饰符只能修饰类、成员变量、成员方法,不能对包使用,很多包可能用户根本不需要,而是程序内部的运行需要,且第三方只要导入项目就能完整看到所有的源代码,还能直接使用这些类,JDK9能对包进行隐藏,同时隐藏包中的所有类

      强封装,每一个模块都声明哪些包是公开的,哪些类是内部使用的,java编译和运行通过实施这些规则来确保外部模块无法使用内部类型

  2. 实例

    • 需求:创建一个ModuleA模块,在ModuleB模块进行访问

      核心就是在输出模块和输入模块使用一个module-info.java的文件来定义模块输出规则和模块输入规则

      • 创建一个ModuleA,然后创建两个包,com.itheima.utilscom.itheima.model

      • utils包中创建一个ArrayUtils工具类并创建一个获取数组最大值的方法

      • modle包中创建一个Person

      • 新建一个输出模块信息,只是输出utils包,model包对外隐藏

        • src目录右键 -- new -- module-info.java【模块信息定义文件】 --在名为module-info.java 的文件中定义模块输出规则

          就是在src目录下创建一个名为module-info.java的文件,在文件中指明模块对外输出的包

        • 模块输出规则

          文件中这个模块名字不一定要和真实的模块名字相同,要输出哪个包就exports ...,以下表示只输出utils

      • 创建一个输入模块信息,和上一个是一样的,创建module-info.java文件,配置模块输入规则

        但是此时moduleA会报错,因为代码中还没有用import引入对应的类的包名

      • 创建一个ModuleB,然后新建一个ModuleTest类,测试使用ArrayUtils

        此时就能正常使用模块a中的utils包下的类了,而且不需要像以前一样对模块打包通过手动导入或者通过maven导入就能直接跨模块使用一个类

         

  3. 优点总结

    • 模块化,凡是没有输出的模块或者没有引入的模块对应的包和类都不会对应导入当前模块

     

 

交互式编程

jdk9使用jshell工具实现的交互式编程,交互式编程的作用是让开发人员马上看到代码的运行效果

  1. 交互式编程

    • java代码的编程模式是:编辑、保存、编译、运行、调试;开发过程中想要实时看到结果原来需要把代码块单独写到main方法中运行,降低开发效率

    • 交互式编程指不需要编写类、直接声明变量、方法或者执行语句就能不通过编译马上看到运行结果

  2. jshell工具的使用

    • jdk9中的bin目录下自带jshell工具jshell.exe,使用该工具需要确保当前系统环境是jdk9以上的版本,使用命令java -version能查看当前系统的jdk版本

    • 在环境变量配置合理的情况下CMD窗口使用命令jshell能直接打开该工具,

      可以在该工具中定义变量,写一句就会执行一句,且可以定义方法,方法定义以后可以直接通过方法名调用,直接方法名就可以调用,实例方法都不需要对象调用,有点像js,就是声明了有这个东西,可以直接拧出来用,且实时展示效果

      • 用java代码定义变量和方法

      • 像js一样直接通过方法名对方法进行调用

    • /list命令是显示当前窗口已经正确执行过的代码

    • /methods命令是查看当前窗口已经定义过的方法

    • /var命令是查看当前窗口已经定义过的变量

    • /edit命令是打开一个类似文本编辑器的窗口界面,里面包含当前窗口已经正确执行过的java语句并且可以在其中继续编辑后续语句,编辑完成后点击accept【一定要点击才能生效】就能在CMD窗口中自动执行并显示执行效果,窗口中的语句编辑可以换行

    • /open 代码块绝对路径命令可以执行文件中的定义变量和方法以及调用方法的语句,课堂演示文件名后缀还是使用的.java,相当于引入了外部以.java作为后缀的文件内容到内部编辑器中

    • /imports命令可以查看当前java默认导入的包

    • /imports java.utils.*可以额外导入没有导入的包,这里utils包实际已经导入,只是作为演示,这个包是否必须在某个目录下?

    • /exit命令是退出jshell

 

接口方法私有化

关注接口中方法私有化的作用以及如何定义调用私有化方法

  1. 接口方法私有化

    • 意义:当接口中的多个默认方法和静态方法中部分代码块多次重复,将这部分代码封装成私有方法供接口内部使用,解决默认方法和静态方法的代码重复冗余问题【因为接口中只有默认方法和静态方法才有方法体】

    • 实例:

       

资源释放代码优化

  1. JDK7及以前传统释放资源的代码

    • 实例

      存在问题:释放资源的代码非常繁杂;而且现在只有一个流,如果流非常多,关闭资源的代码甚至会超过正常的业务代码

  2. JDK8释放资源的代码

    JDK8声明和初始化的代码必须在try后面的小括号中流会自动释放

    • 实例

  3. JDK9释放资源的代码

    JDK9流的声明和初始化可以放在try语句块外面,只需要在try语句块后面的小括号中传递流对象进去即可

    • 实例

       

标识符优化

  1. 优化点

    • JDK8即JDK8以前,下划线_是可以作为一个标识符单独在命名中使用的,但是JDK9中不再允许单独使用_作为标识符进行命名了,JDK9会编译报错

 

String类底层变化

String类内部底层由字符数组变成了字节数组,目的是为了节省内存空间

  1. 以前的String类

    卧槽还真特么是,这不给他面试安排上

    • String类将字符串存储在char数组中,每个字符存储使用两个字节,但是从实际应用数据表明,堆内存中主要就是字符串,大多数字符串对象只包含拉丁-1字符,这样的字符只需要使用一个字节的存储空间,会导致这样的字符串对象内部字符数组的一般空间被闲置

    • 以前的字符串对象实际上是一个char数组private final char value[]

  2. JDK9以后得String对象的底层变成一个byte数组private final byte[] value

  3. 由于String类的变化,现在StringBuilderStringBufferStringBuilder的父类AbstractStringBuilder以及字符串相关的类HotspotVM内部字符串串都采用byte数组了

 

集合工厂方法

JDK9中新增了快速创建只读集合的方法,

  1. 集合【List、Set、Map】中新增静态方法of()可以将不同数量的参数处理返回得到只读的ListSetMap集合

    注意Map的key和value似乎有多种参数类型选择

    • 这种方法创建的只读集合不支持增删改元素,运行时会抛不支持操作异常

    • 创建只读集合实例:

       

 

Stream流新增方法

获取流中不小于50的前几个连续元素、删除流中不小于50的前几个连续元素、创建一个所有元素都可以为nullStream

  1. Stream<T> --> stream.takeWhile(predicate<? super Integer> predicate)

    • 作用:从Stream流中依次获取满足条件的元素,只要遇到第一个不满足条件的元素就停止获取后续元素【后续存在满足条件的元素也不再获取】

    • 实例

  2. Stream<T> --> stream.dropWhile(predicate<? super Integer> predicate)

    • 作用:从Stream流中依次删除满足条件的元素,只要遇到第一个不满足条件的元素就停止删除后续元素【后续存在满足条件的元素也不再删除】

    • 实例

  3. Stream<Object> --> Stream.ofNullable(null)

    • 作用:创建一个所有元素都可以为nullStream

    • 备注说明:JDK8中的Stream不能完全为null,可以存在个别元素是null,否则会报空指针异常;JDK9允许创建元素全为null的Stream流,但是使用count方法统计元素个数仍然显示为0

    • 实例

       

 

支持多分辨率图片

这套API几乎不用,也基本没讲

  1. java.awt.image包下新增支持多分辨率图片的API,实现还是将不同分辨率的图片封装到一张多分辨率的图像中,通过接口的getResolutionVariant方法根据客户端的屏幕大小获取不同分辨率的图像【手机屏幕的大小各个厂商是不同的】

 

全新的HTTPClient

使用浏览器可以发送HTTP请求给网络服务器,使用CURL也可以直接从CMD窗口发送HTTP请求,使用HTTPClient可以直接在程序中发起Http请求,JDK9以前通过HttpURLConnection来获取网络资源,相比于HttpURLConnection只支持HTTP1.0,HTTPClient还支持WebSocketHTTP2.0

  1. 使用HttpClient访问百度服务器得到百度首页

    HttpClient的构造方法被私有化了,需要通过静态方法调用;实际这里讲的很水,HttpClient可以专门来学习一下

    • 实例

  2. HttpClient的使用需要声明输入jdk.incubator.httpclient模块,默认情况下该模块是不能重classpath中获取,需要在module-info.java文件中对对应模块进行引入

    我这里使用的是jdk17版本,应该是有修改,没有配置似乎还是找到了对应的内容并成功响应信息,处理响应内容也使用11中更新的API

    • 配置实例

      jdk9使用HttpClient需要引入的模块

       

 

废弃Applet

JDK9废弃了几个不太常用的功能,主要废弃了Applet API,用于java编写小应用程序,可以直接嵌入到网页,但是现在很少使用java开发客户端,这个类已经很少使用了,而且由于安全问题,主流浏览器取消了对Java浏览器插件的支持【Applet就是用来做浏览器插件的】,HTML5的出现也加速了这个过程

 

Javadoc的HTML5支持

jdk8生成java帮助文档使用HTML4标准,jdk9使用javadoc生成帮助文档时支持HTML5标准,与HTML4的区别是HTML5支持搜索,JDK8的帮助文档没有搜索框,找类需要按包和类进行索引,JDK9的帮助文档有了搜索框,可以直接在搜索框搜索类,这是H5的强大特性【感觉是一个标准,JDK9支持HTML5标准,javadoc生成的帮助文档出现了搜索框】

 

Java动态编译器

动态编译的目的是为了提高编译效率,sjavac【smarter java compilation】在jdk8添加的初级版本,在JDK9对稳定和可靠性进行了优化,能够用来编译任意大型的java项目,sjavacjavac的基础上扩展了增量编译【只编译必要内容,不会执行编译所有的东西就去进行编译】和并行编译【编译期间使用多个核心即并发编译】

但是这个工具没有随OpenJDK一起提供,必须访问http://hg.openjdk.java.net/jsk9/dev/langtools进行获取,当时是因为还没有成熟尚未发布,还在持续升级,现在不知道是不是随OpenJDK一起提供的,后面的JDK新特性再关注

 

钻石操作符升级

语法上JDK8以前不允许约束泛型的尖括号和匿名内部类一起使用,语法上会报错【我这里使用1.8.0_101仍然会编译报错,弹幕说新版JDK8支持这种写法,未验证】

JDK9以后就支持这种写法了

 

JDK10新特性

局部变量类型推断

  1. JDK10以前定义变量存在的问题

    • 以前的变量声明定义必须在变量前声明变量类型

      有些流行的语言可以自动进行类型推断,比如JS

    • JDK10的局部变量类型推断

      语法精简,使用var声明的局部变量会根据右边的字面值推断左边的类型,编译过程会自动将类型推断出来,实际的字节码效果和旧版本效果是一致的

  2. 局部变量类型自动推断使用场景

    • 可以使用的场景

      • 局部变量

      • 循环中的变量

    • 不能使用的场景

      • 成员变量

      • 方法参数

      • 方法体的返回值类型不能用【即返回值类型不能写var】

  3. 注意事项

    • var不是关键字,只是一个保留字,意味着var可以作为类名、方法名...,但是不建议这么使用

    • var定义的变量必须立马赋值,否则无法在编译阶段进行类型推断,编译阶段也无法判断后续是否有赋值,赋值什么类型

    • var不能像类型一样同时定义多个变量

 

代码仓库被合并

JDK10以前完整代码库被分解到多个仓库中,这种多仓库分布在代码管理操作的管理方面做的很差,特别是不可能在相互依赖的变更集的仓库间原子性提交操作变更,比如某个错误修复的代码跨越了多个仓库,无法原子性地对两个仓库进行修改,这种跨越多个存储库的更改是比较常见的,一个仓库就比较容易做到操作原子性提交

 

垃圾回收器接口

这个接口不是给开发者用的,是方便JDK的开发人员在JVM源码中快速集成和移除垃圾回收器

此前垃圾回收器的代码分散,不方便新增垃圾回收器也不利于移除现有垃圾回收器,通过引入一个干净的垃圾回收器接口GC来改善不同垃圾回收器相互之间的隔离,方便后续新增或者移除垃圾回收器,目的是让JVM内部得垃圾回收器代码更好模块化、在不影响当前代码库的情况下方便地向JVM中添加或者移除垃圾回收器

  1. G1回收器引入并行Full GC

    • G1 是设计来作为一种低延时的垃圾回收器。G1收集器还可以进行非常精确地对停顿进行控制。从JDK7开始启用G1垃圾回收器,在JDK9中G1成为默认垃圾回收策略。截止到ava 9,G1的Full GC采用的是单线程算法。也就是说G1在发生Full GC时会严重影响性能 ,JDK10对G1进行了提升,引入了并行Full GC算法, 发生Full GC时可以使用多个线程进行并行回收,用户体验更好

 

应用程序类数据共享

JVM性能增强功能,开发人员无法直接使用

  1. JDK5引入了类数据共享,当多个JVM用到相同的类文件时可以将一组类预处理为共享的存档文件,在运行时将共享存档文件进行内存映射减少启动时间,多个JVM共享同一份存档文件还能减少动态内存占用,但是JDK5只支持引导类加载器加载归档的类

  2. JDK10对应用程序类数据共享进行扩展,允许"应用程序类加载器"、该加载器内置了平台类加载器和自定义类加载器加载已经归档的类

    自己定义的类也可以在JVM中进行共享

  3. 作用

    • 允许不同Java进程间共享通用的类元数据来减少内存占用空间

    • 缩短程序的启动时间

 

线程本地握手

JVM性能增强功能,开发人员无法直接使用

  1. Safepoint【安全点】是Hotpot JVM中让应用程序中所有线程停止的机制,为了做一些非常安全的事情需要让所有线程停下来等待该安全操作执行,比如菜市场人来人往,突然需要清点人数,此时需要让人都不动,方便统计,安全点的作用也是如此

  2. 实现原理是JVM设置一个全局的状态值,应用线程去观察该值,一旦发现JVM将该值设置为所有应用线程停止的状态,就会各自选择一个点停下来,该点就叫安全点,等待所有的应用线程都停下来后,JVM就会开始做一些安全级别非常高的事情【如垃圾清理暂停、类的热更新,偏向锁的取消以及IDE的各种debug操作】

    但是让所有线程都到就近的safepoint停下来需要较长时间且让所有线程都停下来显得比较粗暴

  3. JDK10引入线程本地握手【Thread Local Handshake】,线程本地握手在JVM内部是相当低级别的更改,修改了安全点机制允许在不运行全局虚拟机安全点的情况下实现线程回调,使得部分回调操作只需要停掉单个线程而不是停止所有线程

 

备用存储装置上进行堆内存分配

JVM性能增强功能,开发人员无法直接使用

  1. 现在计算机配置的内存一般都是DRAM【动态随机读取存储器】,速度快但是价格昂贵;近些年出现了NVDIMM【非易失性双列直插式内存模块】,价格便宜,速度比DRAM慢,断电能保留数据,随着NVDIMM廉价内存的可用性,未来系统可能配备异构内存架构【即多种内存】,除了DRAM外还会配备一种或者多种如NVDIMM类型的内存存储器

  2. 该特性的目标是不更改现有应用程序代码的情况下,将以前的DRAM中的堆内存移动到NVDIMM中,其他的内存结构如代码堆、元空间、线程堆栈仍然继续驻留在DRAM中【即使用不频繁的内存放在更便宜的NVDIMM中】

  3. 多JVM部署时,一台机器部署多台JVM,一些JVM【守护程序等服务】的优先级低于其他JVM,低优先级进程可以将堆内存放在NVDIMM中,以牺牲访问延迟的代价允许高优先级进程使用更多的DRAM

  4. 大数据和内存数据库等应用程序对内存的需求不断增长,这样的应用程序可以将堆内存放在NVDIMM中,与DRAM相比容量更大,成本更低

 

实验性JIT编辑器

JVM性能增强功能,开发人员无法直接使用

  1. java程序的整个流程

    • Java编译器指的是JDK自带的javac指令。这一指令可将Java源程序编译成.class字节码文件(bytecode)。字节码无法直接运行,但可以被不同平台JVM中的 interpreter(解释器) 解释形成微处理指令才能执行。由于一个Java指令可能被转译成十几或几十个对等的微处理器指令,这种解释执行模式执行的速度相当缓慢。

  2. JIT编辑器

    • 由于interpreter效率低下,JVM中又增加JIT compiler(即时编译器,just in time)会在运行时有选择性地将运行次数较多的方法直接编译成二进制代码【不再编译成字节码】,直接运行在底层硬件上。花费少许的编译时间来节省稍后相当长的解释执行时间

    • JIT这种设计的确增加不少效率,但是它并未达到最顶尖的效能,因为某些极少执行到的Java指令在JIT编译时所额外花费的时间可能比interpreter解释器执行时的时间还长,针对这些指令而言,整体花费的时间并没有减少。

      就是JIT编辑器编译二进制的时间比解释执行时间长,用的少的代码反而会消耗更多的时间,对于选择哪些代码直接编译成二进制的算法还在研发阶段

    • Graal是基于Java的JIT编译器,这项 JEP 将 Graal 编译器研究项目引入到 JDK 中。

      为了让 JVM 性能与当前 C++ 所写版本匹敌(或有幸超越)提供基础。

 

删除Javah小工具

java可以直接调用c语言或者c++语言写的代码,但是需要一个头文件,javah是用于生成C语言的头文件,JDK8开始,javah的功能已经集成到javac中,所以在JDK10中去掉了该工具

  1. 实例

    • 使用命令javac -h . Helloworld.java对该调用了c语言和c++语言的类进行编译

    • 编译后发现当前目录除了字节码文件还多出来一个.h结尾的头文件,在头文件中生成了一个当前类对应方法的签名

 

 

额外Unicode语言标签扩展

  1. 此前Unicode语言环境扩展仅限日历和数字,该新特性在JDK类中实现了最新规范添加了更多扩展,包括:

    • cu

      货币类型

    • fw

      一周的第一天

    • rg

      区域覆盖

    • tz

      时区

  2. 为了支持这些附加扩展,JDK还对以下API进行更改:

    • java.text.DateFormat::getInstance 将根据扩展名返回实例 ca , rg 和/或 tz

    • java.text.DateFormatSymbols::getInstance 将根据扩展名返回实例 rg

    • java.text.DecimalFormatSymbols::getInstance将根据扩展名返回实例 rg

    • java.text.NumberFormat::get*Instance将根据扩展名 nu 和/或返回实例 rg

    • java.time.format.DateTimeFormatter::localizedBy将返回 DateTimeFormatter 基于扩展情况下 ca ,rg 和/或 tz

    • java.time.format.DateTimeFormatterBuilder::getLocalizedDateTimePattern将根据 rg 扩展名返回模式字符串。

    • java.time.format.DecimalStyle::of将 DecimalStyle 根据扩展名返回实例 nu ,和/或 rg

    • java.time.temporal.WeekFields::of将 WeekFields 根据扩展名 fw 和/或返回实例 rg

    • java.util.Calendar::{getFirstDayOfWeek,getMinimalDaysInWeek}将根据扩展名 fw 和/或返回值 rg

    • java.util.Currency::getInstance 将 Currency 根据扩展名 cu 和/或返回实例 rg

    • java.util.Locale::getDisplayName 将返回一个字符串,其中包括这些U扩展名的显示名称

    • java.util.spi.LocaleNameProvider 这些U扩展的键和类型将具有新的SPI

 

根证书

Open JDK源代码中的密钥库当前为空。因此,默认情况下,TLS等关键安全组件在Open JDK构建中不起作用【感觉就是OracleJDK中的一些组件在OpenJDK中用不了】。JDK10开源Oracle Java SE Root CA程序中的根证书,减少这些构建与Oracle JDK构建之间的差异。

 

时间的发行版本控制

JDK9以后每隔六个月就会严格发布JavaSE和JDK的新版本,之前的命名方案不适合

  1. JDK9以前的版本命名方式

    • 在jdk的bin目录使用命令./java -version能查看当前所在目录jdk的版本信息,以前的版本信息是不带发布时间的,JDK9以后得发布版本带时间信息,如18.9表示2018年9月份,还会显示具体的时间如2018-09-25

 

新增API

集合新增copyOf

JDK10给集合List、Set、Map中新增一个静态方法copyOf,可以复制一个集合中的元素到另一个只读集合中去

  1. 实例:

    • 只读集合的实现原理就是对应的方法直接抛异常

      重写集合中的增删改查方法,直接抛异常,即让集合不能增删改

       

Reader新增transferTo

以前使用IO流复制文件非常麻烦

  1. 使用IO流复制文件实例

    • 实例

      文件的相对路径以当前模块作为相对路径的根路径,缺点是流需要手动关闭且循环读写代码写起来比较麻烦

  2. 使用TransferTo方法复制文件

    • 实例

      这个还是需要自己关闭流,就是省了中间的循环读写代码

    • 源码

      和循环读写的代码差不多,多了空检测

       

 

IO流新增Charset

IO流家族中的PrintStream、PrintWriter、Scannert的构造方法添加了Charset参数,通过charset可以指定IO流操作文本时的编码

  1. IDEA中默认使用的UTF-8编码,在IDEA中使用输出流输出UTF-8编码格式的字符串,内容正常显示,说明PrintStream的默认编码格式也是utf-8

    • 实例

    • 需求,如果此时希望使用GBK编码来打印内容

      Charset是一个抽象类,不能直接构造对象,通过Charset.forName("gbk")给定编码来获取charset对象

      此时会发现数据乱码,因为IDEA默认是UTF-8,但是输出流使用的是GBK,不会自动转换,所以文件解析发生乱码,记事本打开文件另存为的编码属性编码格式ANSI表示使用电脑默认的编码方法,操作系统一般简体中文使用的是GBK的编码格式

 

ByteArrayOutputStream新增toString

无参的toString方法早就有了,这里新增的是toString(Charset charset),确保将byte数组转成字符串的时候编码格式与bytes数组的编码格式一致

 

 

 

附录

  1. Arrays.toString(arr)

    • 作用是打印一个数组的信息,arr.toString()

  2. str.startWith("char")

    • 作用是判断字符串是否以某个字符char或者某个子串char打头

  3. 一般开发使用的是OracleJDKOracleJDK在部分版本上是收费的,但是java8是免费的,2019年以后得OracleJDK是收费的,现在应该有变化,学习Oracle JDK的新特性只需要去学习OpenJDK的新特性即可

  4. java历史

    • 1991年Sun公司推出希望能在各种各种消费型电子产品【机顶盒、冰箱、收音机等】上运行的程序框架Oak,1995年互联网潮流兴起,Oak找到适合发展的市场定位【95年以前以静态网页为主,95年以后动态网页需求激增】

    • 1995年JDK Beta,有很多bug

    • 1996.01--JDK1.0,真正稳定的版本是JDK1.0.2,也被称为Java1【Oak改为Java是因为Oak已经被注册了】

    • 1998年,JDK分为三个版本,应用于桌面环境的J2SE;应用于移动、无线和有限资源情况下的J2ME;应用于基于Java的应用服务器开发的J2EE,J2ME和J2EE是基于J2SE的

    • 2000年,J2SE1.3

    • 2002年,J2SE1.4

    • 2004年,J2SE5.0【推出枚举、泛型、自动装箱拆箱】

    • 2006年,Java SE6

    • 2011年,JavaSE7

    • 2014年,JavaSE8【LST】

    • 2017年,JavaSE9

    • 2018年3月,JavaSE10

    • 2018年9月,JavaSE11【LST】

    • 2019年3月,JavaSE12

    • 2019年9月,JavaSE13

  5. OpenJDK的官网中http://openjdk.java.net/的JEP【JDK Enhancement Proposals】JDK增强建议就是JDK的新特性,进入OpenJDK的官网点击JEP Process就能找到各个版本的JDK新特性,也可以在该页面找到JDK版本点进去Features菜单就是JDK新版本的所有特性

  6. 关注构建特定元素的流的用法IntStream intStream = IntStream.range(0,10);,这种构建方式黑马的新特性课程中没有讲

  7. 关注流式用法stream.filter(Objects::nonNull)即过滤出流中不为null的元素,关注一下Objects类中的相关方法